#include "keyboard.h"
#include "config.h"
#include "util.h"
#include <stdlib.h>

#include <wlr/types/wlr_keyboard.h>

typedef struct {
    uint32_t mod;
    xkb_keysym_t keysym;
    void (*func)(const Arg *);
    const Arg arg;
} Key;

#if 0
/* If you want to use the windows key change this to WLR_MODIFIER_LOGO */
#define MODKEY WLR_MODIFIER_ALT
#define TAGKEYS(KEY,SKEY,TAG) \
    { MODKEY,                    KEY,            view,            {.ui = 1 << TAG} }, \
    { MODKEY|WLR_MODIFIER_CTRL,  KEY,            toggleview,      {.ui = 1 << TAG} }, \
    { MODKEY|WLR_MODIFIER_SHIFT, SKEY,           tag,             {.ui = 1 << TAG} }, \
    { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} }

/* helper for spawning shell commands in the pre dwm-5.0 fashion */
#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }

/* commands */
static const char *termcmd[] = { "alacritty", NULL };
static const char *menucmd[] = { "bemenu-run", NULL };

static const Key keys[] = {
    /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */
    /* modifier                  key                 function        argument */
    { MODKEY,                    XKB_KEY_p,          spawn,          {.v = menucmd} },
    { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return,     spawn,          {.v = termcmd} },
    { MODKEY,                    XKB_KEY_j,          focusstack,     {.i = +1} },
    { MODKEY,                    XKB_KEY_k,          focusstack,     {.i = -1} },
    { MODKEY,                    XKB_KEY_i,          incnmaster,     {.i = +1} },
    { MODKEY,                    XKB_KEY_d,          incnmaster,     {.i = -1} },
    { MODKEY,                    XKB_KEY_h,          setmfact,       {.f = -0.05} },
    { MODKEY,                    XKB_KEY_l,          setmfact,       {.f = +0.05} },
    { MODKEY,                    XKB_KEY_Return,     zoom,           {0} },
    { MODKEY,                    XKB_KEY_Tab,        view,           {0} },
    { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C,          killclient,     {0} },
    { MODKEY,                    XKB_KEY_t,          setlayout,      {.v = &layouts[0]} },
    { MODKEY,                    XKB_KEY_f,          setlayout,      {.v = &layouts[1]} },
    { MODKEY,                    XKB_KEY_m,          setlayout,      {.v = &layouts[2]} },
    { MODKEY,                    XKB_KEY_space,      setlayout,      {0} },
    { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space,      togglefloating, {0} },
    { MODKEY,                    XKB_KEY_e,         togglefullscreen, {0} },
    { MODKEY,                    XKB_KEY_0,          view,           {.ui = ~0} },
    { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag,            {.ui = ~0} },
    { MODKEY,                    XKB_KEY_comma,      focusmon,       {.i = WLR_DIRECTION_LEFT} },
    { MODKEY,                    XKB_KEY_period,     focusmon,       {.i = WLR_DIRECTION_RIGHT} },
    { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less,       tagmon,         {.i = WLR_DIRECTION_LEFT} },
    { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater,    tagmon,         {.i = WLR_DIRECTION_RIGHT} },
    TAGKEYS(          XKB_KEY_1, XKB_KEY_exclam,                     0),
    TAGKEYS(          XKB_KEY_2, XKB_KEY_at,                         1),
    TAGKEYS(          XKB_KEY_3, XKB_KEY_numbersign,                 2),
    TAGKEYS(          XKB_KEY_4, XKB_KEY_dollar,                     3),
    TAGKEYS(          XKB_KEY_5, XKB_KEY_percent,                    4),
    TAGKEYS(          XKB_KEY_6, XKB_KEY_asciicircum,                5),
    TAGKEYS(          XKB_KEY_7, XKB_KEY_ampersand,                  6),
    TAGKEYS(          XKB_KEY_8, XKB_KEY_asterisk,                   7),
    TAGKEYS(          XKB_KEY_9, XKB_KEY_parenleft,                  8),
    { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q,          quit,           {0} },

    /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */
    { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} },
#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} }
    CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6),
    CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12),
};

static int
keybinding(uint32_t mods, xkb_keysym_t sym)
{
    /*
     * Here we handle compositor keybindings. This is when the compositor is
     * processing keys, rather than passing them on to the client for its own
     * processing.
     */
    int handled = 0;
    const Key *k;
    for (k = keys; k < END(keys); k++) {
        if (CLEANMASK(mods) == CLEANMASK(k->mod) &&
                sym == k->keysym && k->func) {
            k->func(&k->arg);
            handled = 1;
        }
    }
    return handled;
}
#endif

/**
 * Get keyboard grab of the seat from sway_keyboard if we should forward events
 * to it.
 *
 * Returns NULL if the keyboard is not grabbed by an input method,
 * or if event is from virtual keyboard of the same client as grab.
 * TODO: see https://github.com/swaywm/wlroots/issues/2322
 */
static struct wlr_input_method_keyboard_grab_v2 *keyboard_get_im_grab(Keyboard* kb) {
    struct wlr_input_method_v2 *input_method = kb->server->input_relay->input_method;
    struct wlr_virtual_keyboard_v1 *virtual_keyboard =
        wlr_input_device_get_virtual_keyboard(kb->device);
    if (!input_method || !input_method->keyboard_grab || (virtual_keyboard &&
                wl_resource_get_client(virtual_keyboard->resource) ==
                wl_resource_get_client(input_method->keyboard_grab->resource))) {
        return NULL;
    }
    return input_method->keyboard_grab;
}

static void
keypressmod(struct wl_listener *listener, void *data)
{
#if 1
    /* This event is raised when a modifier key, such as shift or alt, is
     * pressed. We simply communicate this to the client. */
    Keyboard *kb = wl_container_of(listener, kb, modifiers);
    struct wlr_input_method_keyboard_grab_v2 *kb_grab = keyboard_get_im_grab(kb);
    if (kb_grab) {
        wlr_input_method_keyboard_grab_v2_send_modifiers(kb_grab,
                &kb->device->keyboard->modifiers);
        return;
    }

    /*
     * A seat can only have one keyboard, but this is a limitation of the
     * Wayland protocol - not wlroots. We assign all connected keyboards to the
     * same seat. You can swap out the underlying wlr_keyboard like this and
     * wlr_seat handles this transparently.
     */
    wlr_seat_set_keyboard(kb->server->seat->seat, kb->device);
    /* Send modifiers to the client. */
    wlr_seat_keyboard_notify_modifiers(kb->server->seat->seat,
        &kb->device->keyboard->modifiers);
#endif
}

static void
keypress(struct wl_listener *listener, void *data)
{
    int i;
    /* This event is raised when a key is pressed or released. */
    Keyboard *kb = wl_container_of(listener, kb, key);
    struct wlr_event_keyboard_key *event = data;

    /* Translate libinput keycode -> xkbcommon */
    uint32_t keycode = event->keycode + 8;
    /* Get a list of keysyms based on the keymap for this keyboard */
    const xkb_keysym_t *syms;
    int nsyms = xkb_state_key_get_syms(
            kb->device->keyboard->xkb_state, keycode, &syms);

    int handled = 0;
    uint32_t mods = wlr_keyboard_get_modifiers(kb->device->keyboard);

    wlr_idle_notify_activity(kb->server->idle, kb->server->seat->seat);

#if 0
    /* On _press_ if there is no active screen locker,
     * attempt to process a compositor keybinding. */
    if (!kb->server->input_inhibit_mgr->active_inhibitor
            && event->state == WL_KEYBOARD_KEY_STATE_PRESSED)
        for (i = 0; i < nsyms; i++)
            handled = keybinding(mods, syms[i]) || handled;

    if (!handled) {
#endif
#if 1
        /* if there is a keyboard grab, we send the key there */
        struct wlr_input_method_keyboard_grab_v2 *kb_grab = keyboard_get_im_grab(kb);
        if (kb_grab) {
            wlr_input_method_keyboard_grab_v2_set_keyboard(kb_grab,
                kb->device->keyboard);
            wlr_input_method_keyboard_grab_v2_send_key(kb_grab,
                event->time_msec, event->keycode, event->state);
            return;
        }
#endif
        /* Pass unhandled keycodes along to the client. */
        wlr_seat_set_keyboard(kb->server->seat->seat, kb->device);
        wlr_seat_keyboard_notify_key(kb->server->seat->seat, event->time_msec,
            event->keycode, event->state);
#if 0
    }
#endif
}

static void
cleanupkeyboard(struct wl_listener *listener, void *data)
{
    struct wlr_input_device *device = data;
    Keyboard *kb = device->data;

    wl_list_remove(&kb->link);
    wl_list_remove(&kb->modifiers.link);
    wl_list_remove(&kb->key.link);
    wl_list_remove(&kb->destroy.link);
    free(kb);
}

Keyboard *keyboard_create(struct wlr_input_device *device, Server* server)
{
    Keyboard *kb = ecalloc(1, sizeof(*kb));
    kb->device = device;
    kb->server = server;

    /* Prepare an XKB keymap and assign it to the keyboard. */
    struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
    struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, &xkb_rules,
        XKB_KEYMAP_COMPILE_NO_FLAGS);

    wlr_keyboard_set_keymap(device->keyboard, keymap);
    xkb_keymap_unref(keymap);
    xkb_context_unref(context);
    wlr_keyboard_set_repeat_info(device->keyboard, repeat_rate, repeat_delay);

    kb->modifiers.notify = keypressmod;
    kb->key.notify = keypress;
    kb->destroy.notify = cleanupkeyboard;

    return kb;
}

void keyboard_destroy(Keyboard *keyboard)
{
    free(keyboard);
}
